import os
import yaml
import platform
import atexit
import logging
import logging.config
from flask import Flask, request
from flask_cors import CORS
from ext import db, jwt, scheduler
from app.utils.core import JSONEncoder
# 导入蓝图
from app.apis.redis_task.views import redis_bp
from app.apis.celery_task.views import celery_bp
from app.apis.logs_tasks.views import logs_bp
from app.apis.response_msg_tasks.views import response_msg_bp
from app.apis.celery_task.celery import celery_app

# v1接口
from app.apis.v1.todo.views import todo_v1_bp
from app.apis.v1.users.views import user_v1_bp


# v2接口
# from app.apis.v2.todo import todo_v2_bp
# from app.apis.v2.users import user_v2_bp


def create_app(config_name, config_path=None):
    """工厂函数"""
    app = Flask(__name__)

    # 设置允许跨域
    CORS(app, supports_credentials=True)
    # 读取配置文件
    if not config_path:
        pwd = os.getcwd()
        config_path = os.path.join(pwd, 'config/config.yaml')
    if not config_name:
        config_name = 'PRODUCTION'

    # 读取配置文件
    conf = read_yaml(config_name, config_path)
    app.config.update(conf)

    @app.after_request
    def after_request(response):
        response.headers.add('Access-Control-Allow-Origin', '*')
        if request.method == 'OPTIONS':
            response.headers['Access-Control-Allow-Methods'] = 'DELETE, GET, POST, PUT'
            headers = request.headers.get('Access-Control-Request-Headers')
            if headers:
                response.headers['Access-Control-Allow-Headers'] = headers
        return response

    # celery
    # 更新Celery配置信息
    celery_conf = "redis://{}:{}/{}".format(app.config['REDIS_HOST'], app.config['REDIS_PORT'], app.config['REDIS_DB'])
    celery_app.conf.update({"broker_url": celery_conf, "result_backend": celery_conf})

    # 注册扩展
    register_extensions(app)

    # 启动定时任务scheduler注册
    # 是否开启定时任务
    # 官网
    # https://viniciuschiele.github.io/flask-apscheduler/index.html
    if app.config.get("SCHEDULER_OPEN"):
        scheduler_init(app)
        # scheduler.init_app(app)
        # scheduler.start()

    # 注册蓝图
    register_blueprints(app)

    # 日志设置
    if not os.path.exists(app.config['LOGGING_PATH']):
        # 日志文件目录
        os.mkdir(app.config['LOGGING_PATH'])
    with open(app.config['LOGGING_CONFIG_PATH'], 'r', encoding='utf-8') as f:
        dict_conf = yaml.safe_load(f.read())

    # 载入日志配置
    logging.config.dictConfig(dict_conf)

    # 读取响应消息配置
    with open(app.config['RESPONSE_MESSAGE'], 'r', encoding="utf-8") as f:
        msg_conf = yaml.safe_load(f)
    app.config.update(msg_conf)

    # 返回json格式转换
    app.json_encoder = JSONEncoder

    return app


def register_extensions(app):
    """注册扩展"""
    # 数据库注册
    db.init_app(app=app)
    # jwt注册
    jwt.init_app(app=app)


def register_blueprints(app):
    """注册蓝本"""
    # task
    app.register_blueprint(todo_v1_bp)
    # app.register_blueprint(todo_v2_bp)
    
    # user
    app.register_blueprint(user_v1_bp)
    # app.register_blueprint(user_v2_bp)
    
    # redis
    app.register_blueprint(redis_bp)
    # celery
    app.register_blueprint(celery_bp)
    # logs
    app.register_blueprint(logs_bp)
    # response_msg
    app.register_blueprint(response_msg_bp)


def read_yaml(config_name, config_path):
    """
    config_name:需要读取的配置内容
    config_path:配置文件路径
    """
    if config_name and config_path:
        with open(config_path, 'r', encoding='utf-8') as f:
            conf = yaml.safe_load(f.read())
        if config_name in conf.keys():
            return conf[config_name.upper()]
        else:
            raise KeyError('未找到对应的配置信息')
    else:
        raise ValueError('请输入正确的配置名称或配置文件路径')


def scheduler_init(app):
    """
    保证系统只启动一次定时任务
    参考文献：https://www.cnblogs.com/xujunkai/p/13998637.html
    :param app:
    :return:
    """
    if platform.system() != 'Windows':
        fcntl = __import__("fcntl")
        f = open('scheduler.lock', 'wb')
        try:
            fcntl.flock(f, fcntl.LOCK_EX | fcntl.LOCK_NB)
            scheduler.init_app(app)
            scheduler.start()
            app.logger.debug('Scheduler Started,---------------')
        except:
            pass

        def unlock():
            fcntl.flock(f, fcntl.LOCK_UN)
            f.close()

        atexit.register(unlock)
    else:
        msvcrt = __import__('msvcrt')
        f = open('scheduler.lock', 'wb')
        # 上线更改........
        try:
            msvcrt.locking(f.fileno(), msvcrt.LK_NBLCK, 1)
            scheduler.init_app(app)
            scheduler.start()
            app.logger.debug('Scheduler Started,----------------')
        except Exception as e:
            print(e)
            pass

        def _unlock_file():
            try:
                f.seek(0)
                msvcrt.locking(f.fileno(), msvcrt.LK_UNLCK, 1)
            except:
                pass

        atexit.register(_unlock_file)
